/***
 * * PROJECT
 * *    Logical Neighborhoods - J2ME Run-Time Support 
 * * VERSION
 * *    $LastChangedRevision: 125 $
 * * DATE
 * *    $LastChangedDate: 2008-06-28 14:07:26 +0200 (Sat, 28 Jun 2008) $
 * * LAST_CHANGE_BY
 * *    $LastChangedBy: lmottola $
 * *
 * *    $Id: LogicalNeighborhoods.java 125 2008-06-28 12:07:26Z lmottola $
 * *
 * *   Logical Neighborhoods - 
 * *                   Programming Wireless Sensor Networks
 * *                   (One Slice at a Time)
 * *
 * *   This program is free software; you can redistribute it and/or
 * *   modify it under the terms of the GNU Lesser General Public License
 * *   as published by the Free Software Foundation; either version 2.1
 * *   of the License, or (at your option) any later version.
 * *
 * *   This program is distributed in the hope that it will be useful,
 * *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 * *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * *   GNU General Public License for more details.
 * *
 * *   You should have received a copy of the GNU Lesser General Public 
 * *   License along with this program; if not, you may find a copy at 
 * *   the FSF web site at 'www.gnu.org' or 'www.fsf.org', or you may 
 * *   write to the Free Software Foundation, Inc., 59 Temple 
 * *   Place - Suite 330, Boston, MA  02111-1307, USA
 ***/

package atag.runtime.ln;

import java.util.Random;
import java.util.Vector;

import atag.runtime.ln.neighborhoodDefs.*;
import atag.runtime.ln.nodeAttributes.Node;
import atag.runtime.ln.messages.*;

import atag.runtime.ln.SSD.SSDAttribute;
import atag.runtime.ln.nodeAttributes.NodeAttribute;
import atag.runtime.ln.nodeAttributes.StringSetAttribute;

/**
 * This class represents the entry point for applications. Essentially, users
 * should be using the object constructor and <code>send</code> methods,
 * without touching anything else under polimi.ln.*.
 * 
 * @author Luca Mottola <a
 *         href="mailto:mottola@elet.polimi.it">mottola@elet.polimi.it</a>
 * 
 */
public class LogicalNeighborhoods implements LNMessageListener {

	protected NeighborSet neighborSet;

	protected SSD ssd;

	protected LNRouting appRouting;

	protected Interface[] objectIO;

	private static final int PROFADV_DISSEMINATION = 20000;

	protected static final int EXPIRATION_TICKS = 3;

	public static final int PORT = 100;

	protected static final byte DISSEMINATION_FAN_OUT = 3;

	private static final int MAX_DELAY = 250;

	protected Random rnd;

	private LNDeliver appDeliver;

	protected Node nodeInfo;

	protected Vector messageQueue;

	protected long ticks;

	/**
	 * Creates a new instance of the Logical Neighborhoods run-time based on the
	 * information in a logical node.
	 * 
	 * @param info
	 *            the logical node instance.
	 */
	public LogicalNeighborhoods(Node info) {

		this.ticks = 0;
		this.neighborSet = new NeighborSet(EXPIRATION_TICKS);
		this.nodeInfo = info;
		this.ssd = new SSD(info, this);
		this.appRouting = new LNRouting(info, neighborSet,
				DISSEMINATION_FAN_OUT, this);
        //This initialisation must be done from a settings file
        this.objectIO = new Interface[1];
        // get rid of this bound
        String ip[] = new String [10];
        /*initialisation of objectio must be done via a settings file*/
        for (int ai=0 ; ai< info.getNodeAttributes().size(); ai++) {
			NodeAttribute attr = (NodeAttribute) info.getNodeAttributes().elementAt(ai);
			if (attr.getKey().equals("ip")) {
				StringSetAttribute st = (StringSetAttribute)attr;
                Vector v = (Vector)st.getValue();
                for (int i=0;i<v.size();i++)
                {
                    ip[i] = new String();
                    ip[i]= (String)v.elementAt(i);
                }
			}
		}

		this.objectIO[0] = new IPInterfaceIO(6000, this, 1,ip);

        //this.objectIO[1] = new IPInterfaceIO(PORT, this);
		this.rnd = new Random(info.getMyId());
		this.messageQueue = new Vector();

		// Start receiver
        for(int i=0;i<this.objectIO.length;i++)
        {
            this.objectIO[i].start();
        }

		// Starts periodic ticks
		new Thread(new Runnable() {

			public void run() {
				tickTask();
			}
		}).start();

		// Start outgoing queue
		Thread queue = new Thread(new Runnable() {

			public void run() {
				try {
					queueSendTask();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		});
		queue.start();

        // start profile advertisement

        Thread profadv = new Thread(new Runnable() {

			public void run() {
				try {
					disseminateProfAdv();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		});

        profadv.start();

//        Predicate pred[] = new StringSimplePredicate[2];
//        pred[0] = new StringSimplePredicate("A",0,"a");
//        pred[1] = new StringSimplePredicate("B",0,"b");
//        ConjunctiveNeighborhood CN = new ConjunctiveNeighborhood(pred);
//
//        TestGUI test1 = new TestGUI("Test1",CN,0,0);
//
//        Predicate pred1[] = new StringSimplePredicate[2];
//        pred1[0] = new StringSimplePredicate("A",0,"a");
//        pred1[1] = new StringSimplePredicate("D",0,"d");
//        ConjunctiveNeighborhood CN1 = new ConjunctiveNeighborhood(pred1);
//
//        TestGUI test2 = new TestGUI("Test2",CN1,500,0);
	}

    // disseminates prof adv
    public void disseminateProfAdv() throws InterruptedException
    {
        while (true)
        {
           Thread.sleep(this.rnd.nextInt(PROFADV_DISSEMINATION));
           this.ssd.disseminateProfAdv(ticks);
        }
    }

	/**
	 * Sends a serializable object to a set of neighborhoods. The neighborhoods
	 * are processed individually, so no merging occurs for different (and
	 * possibly overlapping) neighborhoods.
	 * 
	 * @param data
	 *            the object to send.
	 * @param scopes
	 *            a series of neighborhoods the object must be sent to.
	 */
	public void send(byte[] data, Neighborhood[] scopes) {
		appRouting.generateAppMsg(data, scopes);
	}

	/**
	 * Sends a serializable object to a specific node in reply to some previous
	 * message. Note that this cannot be used for general unicast communication.
	 * It can only be used to send some data back to a node that originally
	 * addressed a logical neighborhood.
	 * 
	 * @param data
	 *            the object to send.
	 * @param destination
	 *            the id of the destination node.
	 */
	public void sendReply(byte[] data, int destination) {
		appRouting.generateReplyMsg(data, destination);
	}

	public void messageReceived(LNMessage msg, int interfaceid) {
        /* the commented part below is an exaple of setting up a virtual connection
         * the node executing the commented line would receive messages either from node 0 or from
         * node 5 through the IP interface (interfaceid = 1 for IP, interfaceid = 0 for radio)
         * and doesn't receive from any of the other nodes
          */
        //if(msg.getSenderId() == 0 || (msg.getSenderId()==5 && interfaceid == 1))
        {
		 if (msg.getOriginatorId() != nodeInfo.getMyId()
				&& msg.getSenderId() != nodeInfo.getMyId()) {
			neighborSet.update(msg.getSenderId(), ticks);
			if (msg instanceof ProfAdv) {
			  nodeInfo.debugPrint("Got ProfAdv message from "
			 + msg.getSenderId() + " generated from "
			 + msg.getOriginatorId());
              ((ProfAdv) msg).setInterfaceId(interfaceid);
			 ssd.parseProfAdv((ProfAdv) msg, ticks);
			 } else
			if (msg instanceof AppMsg /*&& ((AppMsg)msg).getNextHop() == nodeInfo.getMyId()*/) {
				nodeInfo.debugPrint("Got AppMsg message from "
						+ msg.getSenderId() + " generated from "
						+ msg.getOriginatorId());
				appRouting.parseAppMsg((AppMsg) msg);
			} else if (msg instanceof ReplyMsg) {
				nodeInfo.debugPrint("Got ReplyMsg message from "
						+ msg.getSenderId() + " generated from "
						+ msg.getOriginatorId());
				appRouting.parseReplyMsg((ReplyMsg) msg);
			} else {
				nodeInfo.debugPrint("Unknown message received!");
			}
		 }

        }
	}

	protected void enqueueMessage(LNMessage msg) {
		msg.setSenderId(nodeInfo.getMyId());
		synchronized (messageQueue) {
			messageQueue.addElement(msg); // Adds at the end of the vector
			messageQueue.notifyAll();
		}
	}

	protected void forwardMessage(LNMessage msg) {
		msg.setHops((byte) (msg.getHops() + 1));
		enqueueMessage(msg);
	}

	private void tickTask() {
		while (true) {
			ticks++;
			neighborSet.pruneExpiredNeighbors(ticks);
			// TODO: Integrate SSD
			ssd.pruneExpiredEntries(ticks);
            ssd.printSSD();
//			ssd.disseminateProfAdv(ticks);
			try {
				Thread.sleep(PROFADV_DISSEMINATION);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}

	private void queueSendTask() throws InterruptedException {
		while (true) {
			synchronized (messageQueue) {
				if (messageQueue.isEmpty()) {
					messageQueue.wait();
				}
			}
			// Avoids collisions at physical layer
			Thread.sleep(rnd.nextInt(MAX_DELAY));

            if(messageQueue.firstElement() instanceof AppMsg)
            {
            
                AppMsg msg = (AppMsg)(messageQueue
					.firstElement());
                ConjunctiveNeighborhood cn = (ConjunctiveNeighborhood)msg.getLNScope();
                    int i = this.ssd.SSDMatches(cn);
                    
              if (i != -1)
              {
                  SSDAttribute attr = (SSDAttribute)ssd.ssdData.elementAt(i);
                     msg.setNextHop(attr.decreasingPath);
                                     
                     objectIO[0].sendBroadcastObject((LNMessage) messageQueue
					 .firstElement());
                
                }
            }
            else
            {
                for (int i = 0; i < this.objectIO.length; i++)
                {
                 objectIO[i].sendBroadcastObject((LNMessage) messageQueue
					.firstElement());
                }
            }
            
			messageQueue.removeElementAt(0);
        }
		
	}

	protected void deliver(byte[] data) {
		nodeInfo.debugPrint("Delivering data item");
		appDeliver.deliver(data);
	}

	public void setReceiver(LNDeliver appDeliver) {
		this.appDeliver = appDeliver;
	}
}
